<?php
class HypergryphGachaAPIError extends Exception {
    public function getLogger(): string
    {
        $time = date("Y-m-d H:i:s");
        $msg = $this->getMessage();
        $file = $this->getFile();
        $line = $this->getLine();
        $trace = $this->getTraceAsString();
        return "[".$time."][ERROR] ".$msg." | 文件名：".$file." | 在 ".$line." 行\n".$trace."\n";
    }
    public function getLoggerByJson(): string{
        $time = date("Y-m-d H:i:s");
        $msg = $this->getMessage();
        $file = $this->getFile();
        $line = $this->getLine();
        $trace = $this->getTraceAsString();
        return json_encode([
            "code" => 1,
            "time" => $time,
            "msg" => $msg,
            "file" => $file,
            "line" => $line,
            "trace" => $trace,
        ], JSON_UNESCAPED_UNICODE);
    }
}
class ServerChannelSelect extends Exception {
    public function channelSelect($c): string{
        $msg = $this->getMessage();
        return json_encode([
            "code" => 114514,
            "msg" => $msg,
            "channel" => $c // 传入可以共选择的服务器
        ], JSON_UNESCAPED_UNICODE);
    }
}

class HypergryphGachaAPI {
    private string $token;
    private string $hgId; // 鹰角ID 暂时没用
    private string $token1;
    private string $token2;
    public string $uid; // 明日方舟UID
    private string $serverChannel; // 明日方舟服务器名 官服 bilibili服
    private bool $serverChannelLocked = false; // 锁定服务器选择状态
    private array $userInfo; // 用户信息
    public string $nickName; // 明日方舟游戏内昵称
    private int $newGachaCount = 0; // 获取的新数计数
    private string $akUserCenter;
    private array $akGachaCate; // 获取卡池列类别
    private array $MASSAGE; // 风格化报错文本
    private bool $isConsole = false; // 是否在控制台运行
    private array $poolInfo; // 卡池信息

    /**
     * 构造函数 接收array格式的参数，需要包含token或phoneNumber和password其中一种
     * @param array $param 给token或者账号密码
     * @throws HypergryphGachaAPIError
     */
    public function __construct(array $param) {
        // 确保存在需要的目录
        $it_is_dir = ["./data","./logs"];
        foreach ($it_is_dir as $dir) {
            if(!is_dir($dir)) mkdir($dir);
        }

        // 获取风格化报错文本
        $this->MASSAGE = parse_ini_file("./resource/MASSAGE.ini", true)["amiya"];
        // 初始化token
        if (isset($param['token'])) {
            // 有token则直接写入token
            // 校验token
            if (strlen($param['token']) == 24) {
                $this->token = $param['token'];
            }else {
                throw new HypergryphGachaAPIError($this->MASSAGE['ERROR_TOKEN_FORMAT_ERROR']);
            }
            $this->logger("token: ".$this->token, "DEBUG");
        }elseif (isset($param['phoneNumber']) && isset($param['password'])) {
            // 无token则发送请求获取token
            $this->token = $this->get_token_by_password($param["phoneNumber"], $param["password"]);
            $this->logger("token: ".$this->token, "DEBUG");
        } else {
            // 无token且无phoneNumber和password则抛出异常
            throw new HypergryphGachaAPIError($this->MASSAGE['INIT_PARAMETER_ERROR']);
        }
        // 判断是否为控制台运行
        if (php_sapi_name() == "cli") {
            // cli为控制台，cgi-fcgi为web
            $this->isConsole = true;
        }
        // 读取pool_info.json
        if (is_file("resource/pool_info.json")) {
            $this->poolInfo = json_decode(file_get_contents("resource/pool_info.json"), true);
        }
//        else {
//            throw new HypergryphGachaAPIError($this->MASSAGE['ERROR_POOL_INFO_NOT_FOUND']);
//        }
        try {
            // 获取token1
            $step1 = $this->getToken1();
            $this->token1 = $step1['data']['token'] ?? '';
            $this->hgId = $step1['data']['hgId'] ?? '';
            $this->logger("token1: ".$this->token1, "DEBUG");
            // 获取明日方舟uid
            $step2 = $this->get_arknights_account_info();
            // 适配绑定多账号
            $this->userInfo = $step2['data']['list'][0]['bindingList'];



            // 设置默认显示的账号
            $defaultAccount = $this->userInfo[0];
            if (count($this->userInfo) > 1) {
                // 多账号，寻找是否有设置为默认的账号，有则覆盖掉
                foreach ($this->userInfo as $account) {
                    if ($account['isDefault']) {
                        $defaultAccount = $account;
                        $this->logger("检测到多账号，已识别到默认显示的账号为「{$defaultAccount["channelName"]}」的Dr.「{$defaultAccount["nickName"]}」", "INFO");
                    }
                }
            }else {
                $this->logger("UID：{$defaultAccount['uid']}，昵称：{$defaultAccount['nickName']}", "INFO");
            }
            // 改变服务器选择
            if (array_key_exists("channel", $param)){
                $selectChannel = $param["channel"];
                foreach ($this->userInfo as $value){
                    if ($value["channelName"] == $selectChannel) {
                        $this->serverChannel = $param["channel"];
                        $this->serverChannelLocked = true; //锁定选择的服务器
                        $defaultAccount = $value;// 选择的服务器
                        $this->logger("更具选择的「{$selectChannel}」，已识别到默认显示的账号为「{$defaultAccount["channelName"]}」的Dr.「{$defaultAccount["nickName"]}」", "INFO");
                    }
                }
                // 检查是否已经上锁，没上锁则该用户不存在该服务器的账号
                if (!$this->serverChannelLocked) {
                    throw new HypergryphGachaAPIError(sprintf($this->MASSAGE['ERROR_CHANNEL_NAME_NOT_EXISTS'], $selectChannel));
                }
            }
            $this->uid = $defaultAccount['uid'] ?? '';
            $this->nickName = $defaultAccount['nickName'] ?? '';
            $this->serverChannel = $defaultAccount['channelName'] ?? '';

            // 先检查是否已经上锁
            if (!$this->serverChannelLocked) {
                // 检测到多个账号则直接返回，并返回状态
                if (count($step2['data']['list'][0]['bindingList']) > 1) {
                    throw new ServerChannelSelect(sprintf($this->MASSAGE["MULTI_ACCOUNT_AUTO_SELECTED"],$this->uid, $this->nickName, $this->serverChannel));
                }
            }

            // 获取token2
            $step3 = $this->getToken2();
            $this->token2 = $step3['data']['token'] ?? '';
            $this->logger("token2: ".$this->token2, "DEBUG");
            // 获取akUserCenter
            $this->get_cookies();
            $this->logger("akUserCenter: ".$this->akUserCenter, "DEBUG");
            // 获取卡池列类别
            $this->getGachaCate();
//            print_r($this->akGachaCate);
            // 获取卡池数据并输出
//            $this->outputGachaData();
        } catch (ServerChannelSelect $e) {
            http_response_code(202);
            echo $e->channelSelect($this->userInfo);
            exit();
        } catch (HypergryphGachaAPIError $e){
            http_response_code(400);
            if ($this->isConsole){
                echo $e->getLogger();
            }else{
                echo $e->getLoggerByJson();
            }
            exit();
        }
    }

    /**
     * 基于账号密码登录
     * @param $phoneNumber
     * @param $password
     * @return string token
     * @throws HypergryphGachaAPIError
     */
    private function get_token_by_password($phoneNumber, $password): string
    {
        // API
        $url = 'https://as.hypergryph.com/user/auth/v1/token_by_phone_password';
        // 请求参数
        $data = [
            'phone' => $phoneNumber,
            'password' => $password
        ];
        // 发送请求
        $response = $this->sendRequest($url, 'POST', $data);
//        print_r($response);
        // 判断返回状态
        if ($response['status'] === 0) {
            // 返回token
            return $response["data"]["token"];
        }elseif ($response['status'] === 1) {
            // 需要人机验证
            throw new HypergryphGachaAPIError($this->MASSAGE['ERROR_LOGIN_ERROR']);
        }elseif ($response['status'] === 100) {
            // 账号或密码错误
            throw new HypergryphGachaAPIError($this->MASSAGE['ERROR_LOGIN_ERROR']);
        }else {
            // 其他错误
            throw new HypergryphGachaAPIError(
                sprintf(
                    $this->MASSAGE['ERROR_UNKNOWN_HYPERGRYPH_API_REQUEST_ERROR'],
                    $response['msg'],
                    $response['status'],
                    $response['type']
                )
            );
        }
    }

    /**
     * 获取token1和鹰角id
     * @return array
     * @throws HypergryphGachaAPIError
     */
    private function getToken1(): array
    {
        // API
        $url = 'https://as.hypergryph.com/user/oauth2/v2/grant';
        $data = [
            'token' => $this->token,
            'appCode' => 'be36d44aa36bfb5b',
            'type' => 1
        ];
        $response = $this->sendRequest($url, 'POST', $data);
        if ($response['status'] === 0){
            // 成功
            return $response;
        }elseif ($response['status'] === 3){
            // token过期
//            print_r($response);
//            $this->customError($this->MASSAGE['ERROR_TOKEN_EXPIRED']);
            throw new HypergryphGachaAPIError($this->MASSAGE['ERROR_TOKEN_EXPIRED']);
        }else {
            // 其他错误
            print_r($response);
//            $this->customError($this->MASSAGE['DEFAULT_UNKNOWN_ERROR']);
            throw new HypergryphGachaAPIError($this->MASSAGE['DEFAULT_UNKNOWN_ERROR']);
        }
    }

    /**
     * 用token1和appCode获取明日方舟账号信息
     * @return array
     * @throws HypergryphGachaAPIError
     */
    private function get_arknights_account_info(): array
    {
        $url = 'https://binding-api-account-prod.hypergryph.com/account/binding/v1/binding_list?'
            . http_build_query([
                "token" => $this->token1,
                "appCode" => "arknights"
            ]);
        $response = $this->sendRequest($url);
        if ($response['status'] === 0) {
            return $response;
        }else {
            print_r($response);
            throw new HypergryphGachaAPIError($this->MASSAGE['DEFAULT_UNKNOWN_ERROR']);
        }
    }

    /**
     * 用token1和uid获取token2
     * @return array
     * @throws HypergryphGachaAPIError
     */
    private function getToken2(): array
    {
        $url = 'https://binding-api-account-prod.hypergryph.com/account/binding/v1/u8_token_by_uid';
        $data = [
            'token' => $this->token1,
            'uid' => $this->uid
        ];
        $response = $this->sendRequest($url, 'POST', $data);
        if ($response['status'] === 0) {
            return $response;
        }else {
            print_r($response);
            throw new HypergryphGachaAPIError($this->MASSAGE['DEFAULT_UNKNOWN_ERROR']);
        }
    }

    /**
     * 用token2获取cookie
     * @return void
     */
    private function get_cookies(): void
    {
        $url = 'https://ak.hypergryph.com/user/api/role/login';
        $data = [
            'token' => $this->token2,
            'source_from' => '',
            'share_type' => '',
            'share_by' => ''
        ];
        $headers = [];
        $response = $this->sendRequest($url, 'POST', $data, $headers, true);

        // 解析Cookies
        $this->parseCookies($headers);
    }

    /**
     * 辅助函数，处理cookie保存需要的信息
     * @param $headers
     * @return void
     */
    private function parseCookies($headers): void
    {
        foreach ($headers as $header) {
            if (strpos($header, 'set-cookie:') === 0 || strpos($header, 'Set-Cookie:') === 0) {
                $cookie = substr($header, 12);
                foreach (explode(';', $cookie) as $item) {
                    $parts = explode('=', trim($item), 2);
                    if ($parts[0] === 'ak-user-center') {
                        $this->akUserCenter = $parts[1];
                    }
                }
            }
        }
    }

    /**
     * 获取抽卡分类
     * @return void
     */
    private function getGachaCate(): void
    {
        $url = 'https://ak.hypergryph.com/user/api/inquiry/gacha/cate?uid='.$this->uid;
        $headers = [
            'Cookie: ak-user-center='.$this->akUserCenter,
            'x-account-token: '.$this->token,
            'x-role-token: '.$this->token2
        ];
        $response = $this->sendRequest($url, 'GET', [], $headers);
        $this->akGachaCate = $response['data'];
    }

    /**
     * 获取抽卡历史
     * @param string $category 抽卡分类
     * @param int $size 每页数量
     * @param int|null $gachaTs 上一页数据最后一抽时间
     * @param int|null $pos 上一页数据最后一抽是十连中的第几抽
     * @return array
     */
    public function getGachaHistory(string $category, int $size = 10, int $gachaTs = null , int $pos = null): array
    {
        $url = 'https://ak.hypergryph.com/user/api/inquiry/gacha/history?'
            . http_build_query([
                'uid' => $this->uid,
                'category' => $category,
                'size' => $size,
                'gachaTs' => $gachaTs, // 上一页数据最后一抽时间
                'pos' => $pos // 上一页数据最后一抽是十连中的第几抽
            ]);

        $headers = [
            'Cookie: ak-user-center='.$this->akUserCenter,
            'x-account-token: '.$this->token,
            'x-role-token: '.$this->token2
        ];
        return $this->sendRequest($url, 'GET', [], $headers);
    }

//    public function OgetGachaHistoryAll(): array
//    {
//
//        // 所有抽卡数据
//        $gachaData = $this->getOldData() ?? array();
//        foreach ($this->akGachaCate as $category) {
//            echo "正在获取 ".$category['name']." 的抽卡历史\n";
//            // 判断是否有上一抽
//            if ($gachaData[$category['id']] ?? false) {
//                $lastGachaTime = array_reverse($gachaData[$category['id']])[0]['gachaTs'];
//                $lastGachaPos = array_reverse($gachaData[$category['id']])[0]['pos'];
//            }else{
//                $lastGachaTime = 0;
//                $lastGachaPos = 0;
//            }
//            // 遍历每一个Cate
//            $categoryHistory = $this->getGachaHistory($category['id']);
//            if ($categoryHistory['code'] == 0) {
//                // 检查是否有已经获取的数据
//                for ($i = 0; $i < count($categoryHistory["data"]["list"]); $i++) {
//                    $gacha = $categoryHistory["data"]["list"][$i];
//                    if ($this->compareGachaTsAndPos($lastGachaTime, $lastGachaPos, $gacha['gachaTs'], $gacha['pos'])) {
//                        // 截到$i前面的数组
//                        $categoryHistory['data']['list'] = array_slice($categoryHistory['data']['list'], 0, $i);
//                        $hasMore = false;
//                    }else{
//                        $hasMore = $categoryHistory['data']['hasMore'];
//                    }
//                }
//                // 需要反转获取到的数据，然后存入对应数组
//                $gachaData[$category['id']] = array_reverse($categoryHistory['data']['list']);
//            }else {
//                $hasMore = false;
//            }
//            // 如果还有下一页
//            while ($hasMore) {
//                print_r($gachaData[$category['id']]);
//                $next_page =  $this->getGachaHistory(
//                    $category['id'],
//                    10,
//                    $gachaData[$category['id']][0]['gachaTs'],
//                    $gachaData[$category['id']][0]['pos']
//                );
//                if ($categoryHistory['code'] == 0) {
//                    // 检查是否有已经获取的数据
//                    for ($i = 0; $i < count($categoryHistory["data"]["list"]); $i++) {
//                        $gacha = $categoryHistory["data"]["list"][$i];
//                        if ($this->compareGachaTsAndPos($gachaData[$category['id']][0]['gachaTs'], $gachaData[$category['id']][0]['pos'], $gacha['gachaTs'], $gacha['pos'])) {
//                            // 截到$i前面的数组
//                            $categoryHistory['data']['list'] = array_slice($categoryHistory['data']['list'], 0, $i);
//                            $hasMore = false;
//                        }else{
//                            $hasMore = $categoryHistory['data']['hasMore'];
//                        }
//                    }
//                    $gachaData[$category['id']] = array_merge(array_reverse($next_page['data']['list']), $gachaData[$category['id']]);
//                }
//            }
//        }
//        return $gachaData;
//    }

    /**
     * 返回基于卡池分类的从旧到新排序的数组（包含已保存的数据）
     * @return array
     */
    public function getGachaHistoryAll(): array {
        // 读取旧数据
        $oldGacha = $this->getOldData() ?? [];
        $resGacha = $oldGacha;
        // 遍历每一个Cate
        foreach ($this->akGachaCate as $category) {
            $categoryId = $category['id'];
            $oldCategoryGacha = $oldGacha[$categoryId] ?? [];

            // 初始化旧数据指针
            if (!empty($oldCategoryGacha)) {
                // 有旧数据
                $oldGachaLast = end($oldCategoryGacha);
                $oldGachaTs = $oldGachaLast["gachaTs"];
                $oldGachaPos = $oldGachaLast["pos"];
            } else {
                // 无旧数据
                $oldGachaTs = 0;
                $oldGachaPos = 0;
            }

            $hasMore = true;
            $currentPageTs = 0;  // 分页游标（时间戳）
            $currentPagePos = 0; // 分页游标（位置）
            $newCategoryGacha = [];
            $getCount = 0;

            while ($hasMore) {
                $getCount++;
                $category['name'] = str_replace("\n"," ", $category['name']);
                $this->logger("正在获取 ".$category['name']." 的 第".$getCount."页 抽卡历史", "INFO");
                // 始终使用当前分页游标获取数据
                $newGacha = $this->getGachaHistory(
                    $categoryId,
                    10,
                    $currentPageTs,
                    $currentPagePos
                );
                // 检查是否出错
                if ($newGacha['code'] != 0) {
                    return [
                        'code' => $newGacha['code'],
                        'msg' => $newGacha['msg'],
                        'originalData' => $newGacha
                    ];
                }

                // 当没有数据时提前终止
                if (empty($newGacha['data']['list'])) {
                    $hasMore = false;
                    continue;
                }

                // 获取本页最后一条记录
                $newGachaLast = end($newGacha['data']['list']);
                $newGachaTs = $newGachaLast['gachaTs'];
                $newGachaPos = $newGachaLast['pos'];
                // 判断是否需要停止抓取（新旧数据重叠）
                if ($this->compareGachaTsAndPos(
                    $oldGachaTs,
                    $oldGachaPos,
                    $newGachaTs,
                    $newGachaPos
                )) {
                    $hasMore = false;
                    // 截取旧数据之前的新记录
                    foreach ($newGacha['data']['list'] as $item) {
                        if ($this->compareGachaTsAndPos(
                            $oldGachaTs,
                            $oldGachaPos,
                            $item['gachaTs'],
                            $item['pos']
                        )) {
                            break;
                        }
                        $newCategoryGacha[] = $item;
                        $this->newGachaCount++;// 新数据计数+1
                    }
                } else {
                    // 合并完整页数据
                    $newCategoryGacha = array_merge(
                        $newCategoryGacha,
                        $newGacha['data']['list']
                    );
                    $this->newGachaCount += count($newGacha['data']['list']);// 新数据计数增加
                    $hasMore = $newGacha['data']['hasMore'];
                }

                // 更新分页游标（即使hasMore=false也要更新）
                $currentPageTs = $newGachaTs;
                $currentPagePos = $newGachaPos;
            }

            // 合并新旧数据（新数据需要反转，旧数据保持倒序）
            $resGacha[$categoryId] = array_merge(
                $oldCategoryGacha,
                array_reverse($newCategoryGacha)
            );
        }
//        echo "\n".$this->newGachaCount;
        $this->logger("获取到了{$this->newGachaCount}抽新数据","INFO");
        return $resGacha;
    }

//    public function getGachaHistoryAll(): array{
//        // 读取旧数据
//        $oldGacha = $this->getOldData() ?? null;
//        // 返回的数据
//        $resGacha = $oldGacha;
//        // 获取新数据
//        foreach ($this->akGachaCate as $category) {
//            // 遍历每个抽卡分类
//            if ($oldGacha == null || $oldGacha[$category['id']] == null) {
//                echo "无旧数据，不需要获取最后一次抽卡时间";
//                $oldGachaTs = 0;
//                $oldGachaPos = 0;
//            }else {
//                // 判断是否有上一抽
//                if (array_key_exists($category['id'], $oldGacha) && count($oldGacha[$category['id']]) > 0) {
//                    // 有则填入上一抽的时间
//                    $oldGachaLast = $oldGacha[$category['id']][count($oldGacha[$category['id']]) - 1];
//                    $oldGachaTs = $oldGachaLast["gachaTs"];
//                    $oldGachaPos = $oldGachaLast["pos"];
//                }else{
//                    $oldGachaTs = 0;
//                    $oldGachaPos = 0;
//                }
//            }
//            $hasMore = true;
//            $gachaTs = 0;
//            $gachaPos = 0;
//            $newCategoryGacha = [];
//            $getCount = 0;
//            // 遍历每一页
//            while ($hasMore) {
//                if ($getCount > 0) {
//                    // 获取新数据
//                    $newGacha = $this->getGachaHistory(
//                        $category['id'],
//                        10,
//                        $oldGachaTs,
//                        $oldGachaPos
//                    );
//                }else {
//                    // 第一次获取
//                    $newGacha = $this->getGachaHistory(
//                        $category['id'],
//                        10,
//                        $gachaTs, // 默认第一次请求时为0
//                        $gachaPos // 默认第一次请求时为0
//                    );
//                }
//                $getCount++;
//                echo "第".$getCount."页\n";
//                if ($newGacha['code'] == 0) {
//                    // 获取成功
//                    // 获取最新一页的最后一抽时间
//                    $newGachaLast = $newGacha['data']['list'][count($newGacha['data']['list']) - 1];
//                    $newGachaTs = $newGachaLast['gachaTs'];
//                    $newGachaPos = $newGachaLast['pos'];
//                    if ($this->compareGachaTsAndPos($oldGachaTs, $oldGachaPos, $newGachaTs, $newGachaPos)) {
//                        // 判断本页第最后一抽的时间是否早于旧数据（旧数据已经包含本页数据）
//                        // 不需要获取更多数据
//                        $hasMore = false;
//                        // 判断需要截取到的位置
//                        for ($i = 0; $i < count($newGacha['data']['list']); $i++) {
//                            if ($this->compareGachaTsAndPos(
//                                $oldGachaTs,
//                                $oldGachaPos,
//                                $newGacha['data']['list'][$i]['gachaTs'],
//                                $newGacha['data']['list'][$i]['pos']))
//                            {
//                                $sliceGacha = array_slice($newGacha['data']['list'], 0, $i);
//                                break;
//                            }else {
//                                // 理论上代码不会进到这里
//                                $sliceGacha = [];
//                            }
//                        }
//                        // 赋值到返回的数据中
//                        $newCategoryGacha = array_merge($newCategoryGacha, $sliceGacha);
//                    }else {
//                        // 需要获取更多数据
//                        $hasMore = $newGacha['data']['hasMore'];
//                        $newCategoryGacha = array_merge($newCategoryGacha, $newGacha['data']['list']);
//                        $oldGachaTs = $newGachaTs;
//                        $oldGachaPos = $newGachaPos;
//                    }
//                }else {
//                    // 获取失败，直接返回
//                    return array(
//                        'code' => $newGacha['code'],
//                        'msg' => $newGacha['msg'],
//                        'originalData' => $newGacha
//                    );
//                }
//            }
//            // 将该分类的数据并入返回数组
//            $resGacha[$category['id']] = array_merge($oldGacha[$category['id']] ?? [], array_reverse($newCategoryGacha));
//        }
//        return $resGacha;
//    }

    /**
     * 辅助函数，对比gachaTs和pos
     * 如果旧数据的时间戳大于新数据，时间戳相同时旧pos大于新pos，则返回true，否则返回false
     * true表示旧数据包含新数据，不需要获取新数据
     * @return bool
     * @var $oldPos int 旧数据位置
     * @var $NewTs int 新数据时间
     * @var $NewPos int 新数据位置
     * @var $oldTs int 旧数据时间
     */
    private function compareGachaTsAndPos(int $oldTs, int $oldPos, int $NewTs, int $NewPos): bool
    {
        if ($oldTs > $NewTs) {
            return true;
        } elseif ($oldTs == $NewTs) {
            return $oldPos >= $NewPos;
        } else {
            return false;
        }
    }

    public function getFormatGachaData(): array
    {
        // 获取所有抽卡记录
        $gachaHistory = $this->getGachaHistoryAll();
        file_put_contents("./data/".$this->uid.".json", json_encode($gachaHistory, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
        // 建立返回数组结构
        $resGacha = array(
            "uid" => $this->uid,// 用户UID
            "nickName" => $this->nickName,// 用户昵称
            "pool" => array(
                "id" => [], // 用ID来分类
                "name" => [], // 用名字来分类

                "id&name" => [] // ID+名字的键值对
            ),// 卡池列表
            "newDataCount" => $this->newGachaCount,// 新获取的数据计数
            "gachaAll" => []// 所有抽卡记录
            );
        // 遍历以建立卡池列表和历史记录
        foreach ($gachaHistory as $categoryName => $category) {
            $categoryName_zh_cn = array(
                "normal" => "标准寻访",
                "spring_fest" => "春节·庆典",
                "attain" => "跨年",
                "summer_fest" => "夏日·庆典",
                "anniver_fest" => "限定寻访·庆典",
                "linkage" => "联动寻访",
                "classic" => "中坚寻访",
                "newbee" => "新人特惠寻访",
                "mujica" => "梦中舞会 人偶寻访"
            );
            foreach ($category as $gacha) {
                // 建立卡池列表
                $poolId = $gacha['poolId'];
                $poolName = $this->poolInfo[$poolId]["name"];
                // 写入ID+名字的键值对
                if ($poolName !== "标准寻访" && $categoryName == "normal") {
                    // 分离活动寻访
                    $resGacha["pool"]["id&name"]["活动寻访"][$poolId] = $poolName;
                }else {
                    $resGacha["pool"]["id&name"][$categoryName_zh_cn[$categoryName]][$poolId] = $poolName;
                }

                if (!in_array($poolId, $resGacha["pool"]["id"])) {
                    // 如果该卡池ID不在返回数组中，则添加
                    $resGacha["pool"]["id"][] = $poolId;
                }
                if (!in_array($poolName, $resGacha["pool"]["name"])) {
                    // 如果该卡池名不在返回数组中，则添加
                    $resGacha["pool"]["name"][] = $poolName;
                }

                // 检查是否已经有该卡池数据
                if (isset($resGacha["gachaAll"][$poolName])) {
                    // 已有则获取
                    $thisGachaPool = $resGacha["gachaAll"][$poolName];
                }else {
                    // 没有则建立每个卡池的数组结构
                    $thisGachaPool = array(
                        "firstGacha" => 0,// 第一次抽卡时间
                        "lastGacha" => 0,// 最后一次抽卡时间
                        "poolId" => "",// 该卡池ID（标准寻访此项为空）
                        "poolName" => $poolName,// 该卡池名
                        "gachaLastR5Num" => 0,// 距离上次五星出货次数
                        "gachaLastR6Num" => 0,// 距离上次六星出货次数
                        "gachaNum" => 0,// 该卡池抽卡次数
                        "r3Num" => 0,// 三星数量
                        "r4Num" => 0,// 四星数量
                        "r5History" => array(),// 五星历史
                        "r5Num" => 0,// 五星数量
                        "r5Avg" => 0,// 五星平均出货次数
                        "r6History" => array(),// 六星历史
                        "r6Num" => 0,// 六星数量
                        "r6Avg" => 0// 六星平均出货次数
                    );
                }
                // 写入卡池ID
                if ($thisGachaPool["poolName"] !== "标准寻访" && $thisGachaPool["poolName"] !== "中坚寻访") {
                    // 不是标准寻访才会填入卡池ID
                    $thisGachaPool["poolId"] = $poolId;
                }
                // 写入该卡池第一抽
                if ($thisGachaPool["firstGacha"] == 0) {
                    $thisGachaPool["firstGacha"] = $gacha['gachaTs'];
                }
                // 写入该卡池最后一次抽卡
                if ($thisGachaPool["lastGacha"] < $gacha['gachaTs']) {
                    $thisGachaPool["lastGacha"] = $gacha['gachaTs'];
                }
                // 卡池总数+1
                $thisGachaPool["gachaNum"]++;

                switch ($gacha['rarity']) {
                    case 0:
                        // 一星干员（抽卡不会抽出）
                        break;
                    case 1:
                        // 二星干员（抽卡不会抽出）
                        break;
                    case 2:
                        // 三星干员
                        $thisGachaPool["r3Num"]++;
                        $thisGachaPool["gachaLastR5Num"]++;
                        $thisGachaPool["gachaLastR6Num"]++;
                        break;
                    case 3:
                        // 四星干员
                        $thisGachaPool["r4Num"]++;
                        $thisGachaPool["gachaLastR5Num"]++;
                        $thisGachaPool["gachaLastR6Num"]++;
                        break;
                    case 4:
                        // 五星干员
                        $thisGachaPool["r5Num"]++;
                        $thisGachaPool["gachaLastR5Num"]++;
                        $thisGachaPool["gachaLastR6Num"]++;
                        $thisGachaPool["r5Avg"] = round($thisGachaPool["gachaNum"] / $thisGachaPool["r5Num"], 2);
                        // 在单抽的信息中添加距离上一次出货计数
                        $gacha["gachaCount"] = $thisGachaPool["gachaLastR5Num"];
                        // 清零上一次出货计数
                        $thisGachaPool["gachaLastR5Num"] = 0;
                        // 写入抽卡历史
                        array_unshift($thisGachaPool["r5History"], $gacha);
                        break;
                    case 5:
                        // 六星干员
                        $thisGachaPool["r6Num"]++;
                        $thisGachaPool["gachaLastR5Num"]++;
                        $thisGachaPool["gachaLastR6Num"]++;
                        $thisGachaPool["r6Avg"] = round($thisGachaPool["gachaNum"] / $thisGachaPool["r6Num"], 2);
                        // 在单抽的信息中添加距离上一次出货计数
                        $gacha["gachaCount"] = $thisGachaPool["gachaLastR6Num"];
                        // 清零上一次出货计数
                        $thisGachaPool["gachaLastR6Num"] = 0;
                        // 写入抽卡历史
                        array_unshift($thisGachaPool["r6History"], $gacha);
                }
                // 将卡池结构赋值到返回的数据中
                $resGacha["gachaAll"][$poolName] = $thisGachaPool;
                // ID为名字的版本
                if ($poolName !== "标准寻访" && $categoryName == "normal") {
                    // 分离活动寻访
                    $resGacha["gachaAllV3"]["活动寻访"][$poolId] = $thisGachaPool;
                }else {
                    $resGacha["gachaAllV3"][$categoryName_zh_cn[$categoryName]][$poolId] = $thisGachaPool;
                }

            }
        }
        return $this->sortFormatGacha($resGacha);
    }

    /**
     * 获取旧数据
     * @return array|bool
     *
     */
    private function getOldData(): bool|array
    {
        $filePath = "./data/".$this->uid.".json";
        if (file_exists($filePath)) {
            $old_file = json_decode(file_get_contents($filePath), true);
            // 删除文件中的uid和nickname
            unset($old_file["uid"], $old_file["nickName"]);
            return $old_file;
        }else {
            return false;
        }
    }
    /**
     * 辅助函数-按时间排序所有抽卡记录
     * @param $formatGacha
     * @return array
     */
    private function sortFormatGacha($formatGacha): array {

        $poolNameList = $formatGacha["pool"]["name"];
        $list = array();
        while (count($poolNameList) > 1){
            $a = array_shift($poolNameList);
            $a_time = $formatGacha["gachaAll"][$a]["firstGacha"];
            foreach ($poolNameList as $key => $poolName) {
                $b_time = $formatGacha["gachaAll"][$poolName]["firstGacha"];
                if ($b_time > $a_time) {
                    $poolNameList[$key] = $a;
                    $a = $poolName;
                    $a_time = $formatGacha["gachaAll"][$a]["firstGacha"];
                }
            }
            $list[] = $a;
        }
        $formatGacha["pool"]["name"] = $list;
        return $formatGacha;
    }
    /**
     * 辅助函数-将时间戳转换为时间
     * @param $timestamp
     * @return string
     */
    private function timestampToTime($timestamp): string {
        return date('Y-m-d H:i:s', $timestamp / 1000);
    }
    /**
     * 发送HTTP请求
     * @param $url
     * @param $method
     * @param $data
     * @param $headers
     * @param $captureHeaders
     * @return array|mixed
     */
    public function sendRequest($url, $method = 'GET', $data = [], &$headers = null, $captureHeaders = false) {
        // 初始化curl
        $ch = curl_init();
        // 设置请求的URL
        curl_setopt($ch, CURLOPT_URL, $url);
        // 将curl_exec()获取的信息以字符串返回，而不是直接输出
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        // 是否捕获响应头
        curl_setopt($ch, CURLOPT_HEADER, $captureHeaders);
        // CURL证书不信任问题
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);

        if ($method === 'POST') {
            // 设置请求方法为POST
            curl_setopt($ch, CURLOPT_POST, true);
            // 设置POST的数据
            curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
            // 设置默认的请求头
            $defaultHeaders = [
                'Content-Type: application/json',
                'Content-Length: '.strlen(json_encode($data))
            ];
            // 设置请求头
            curl_setopt($ch, CURLOPT_HTTPHEADER, array_merge($defaultHeaders, $headers ?? []));
        } else {
            // 设置请求头
            curl_setopt($ch, CURLOPT_HTTPHEADER, $headers ?? []);
        }

        // 执行请求
        $response = curl_exec($ch);
//        print_r($response);

        if ($captureHeaders) {
            // 获取响应头的大小
            $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
            // 将响应头分割成数组
            $headers = explode("\r\n", substr($response, 0, $headerSize));
            // 将响应体从响应头中分离出来
            $response = substr($response, $headerSize);
        }
        // 关闭curl
        curl_close($ch);
        // 将响应体解析为JSON并返回
        return json_decode($response, true) ?: [];
    }
//    private function customError($message) {
//        // 获取调用堆栈信息
//        $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1);
//
//        // 提取文件名、行号和函数名
//        $file = $backtrace[0]['file'] ?? 'unknown file';
//        $line = $backtrace[0]['line'] ?? 'unknown line';
//        $function = $backtrace[1]['function'] ?? 'unknown function';
//
//        // 构造错误信息
//        $errorMessage = sprintf(
//            "Error: %s\nFile: %s\nLine: %d\nFunction: %s",
//            $message,
//            $file,
//            $line,
//            $function
//        );
//
//        // 输出错误信息
//        return $errorMessage;
//    }

    public function logger($msg, $level)
    {
        $date = date('Y-m-d');
        $log_path = __DIR__."/logs/ALL.log";
        if (!is_file($log_path)){
            file_put_contents($log_path, "");
        }
        if ($level == "DEBUG"){
            return;
        }
        $time = date('Y-m-d H:i:s');
        if ($this->isConsole) {
            echo "[".$time."][".$level."]".$msg."\n";
            file_put_contents($log_path, "[".$time."][".$level."]".$msg."\n", FILE_APPEND);
        }
    }
}

// 使用示例
//if (isset($_GET['token']) && isset($_GET['uid'])) {
//    $api = new HypergryphGachaAPI($_GET['token'], $_GET['uid']);
//    $api->execute();
//}
